<template>
  <div class="webGlPrc4">
    <!-- 光照 -->
    <canvas ref="myGLCanvas" :width="rect?.width" :height="rect?.height"/>
  </div>
</template>

<script lang="ts">
import { Vue, Options } from 'vue-property-decorator';
import { mat4, mat3 } from 'gl-matrix';
@Options({ components: {} })
export default class WebGlPrc4 extends Vue {
  public rect: DOMRect|null = null;
  public gl!: WebGLRenderingContext;
  public shaderProgram: WebGLProgram|null=null;
  public floorVertexPositonBuffer: WebGLBuffer|null = null;
  public floorVertexNormalBuffer: WebGLBuffer|null = null;
  public cubeVertexPositionBuffer: WebGLBuffer|null = null;
  public cubeVertexIndexBuffer: WebGLBuffer|null = null;
  public cubeVertexColorBuffer: WebGLBuffer|null = null;
  public cubeVertexNormalBuffer: WebGLBuffer|null = null;

  public vertexPositionAttribute=0;
  public vertexColorAttribute=0;
  public vertexNormalAttribute=0;
  public uniformMVMatrix!: WebGLUniformLocation;
  public uniformProjectMatrix!: WebGLUniformLocation;
  public uniformNormalMatrixLoc!: WebGLUniformLocation;
  public uniformLightPositionLoc!: WebGLUniformLocation;
  public uniformAmbientLightColorLoc!: WebGLUniformLocation;
  public uniformDiffuseLightColorLoc!: WebGLUniformLocation;
  public uniformSpecularLightColorLoc!: WebGLUniformLocation;

  public floorModelViewMatrix!: mat4;
  public floorProjectionMatrix!: mat4;
  public floorNormalMatrix!: mat3;
  public cubeModelViewMatrix!: mat4;
  public cubeProjectionMatrix!: mat4;
  public cubeNormalMatrix!: mat3;

  public squareRotation=0;

  public draw() {
    this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
    this.drawFloor();
    this.drawCube();
    requestAnimationFrame(() => {
      this.draw();
    });
  }
  public drawCube() {
    this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.cubeVertexPositionBuffer);
    this.gl.vertexAttribPointer(this.vertexPositionAttribute, 3, this.gl.FLOAT, false, 0, 0);
    this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.cubeVertexIndexBuffer);

    this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.cubeVertexNormalBuffer);
    this.gl.vertexAttribPointer(this.vertexNormalAttribute, 3, this.gl.FLOAT, false, 0, 0);

    this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.cubeVertexColorBuffer);
    this.gl.enableVertexAttribArray(this.vertexColorAttribute);
    this.gl.vertexAttribPointer(this.vertexColorAttribute, 3, this.gl.FLOAT, false, 0, 0);

    this.setupCubeTransformMatrix();
    this.gl.drawElements(this.gl.TRIANGLES, 36, this.gl.UNSIGNED_SHORT, 0);
  }
  public setupCubeTransformMatrix() {
    this.squareRotation += 0.01;
    this.cubeModelViewMatrix = mat4.create();
    this.cubeProjectionMatrix = mat4.create();
    mat4.identity(this.cubeModelViewMatrix);
    mat4.lookAt(this.cubeModelViewMatrix, [15, 15, 15], [0, 0, 0], [0, 1, 0]);
    mat4.scale(this.cubeModelViewMatrix, this.cubeModelViewMatrix, [0.5, 0.5, 0.5]);
    mat4.translate(this.cubeModelViewMatrix, this.cubeModelViewMatrix, [8.0, 15.0, 8.0]);
    mat4.rotate(this.cubeModelViewMatrix, this.cubeModelViewMatrix, this.squareRotation, [0, 0, 1]);
    mat4.perspective(this.cubeProjectionMatrix, 70, this.rect!.width / this.rect!.height, 0.1, 100.0);
    this.gl.uniformMatrix4fv(this.uniformMVMatrix, false, this.cubeModelViewMatrix);
    this.gl.uniformMatrix4fv(this.uniformProjectMatrix, false, this.cubeProjectionMatrix);

    this.cubeNormalMatrix = mat3.create();
    mat3.fromMat4(this.cubeNormalMatrix, this.cubeModelViewMatrix);
    mat3.transpose(this.cubeNormalMatrix, this.cubeNormalMatrix);
    this.gl.uniformMatrix3fv(this.uniformNormalMatrixLoc, false, this.cubeNormalMatrix);
  }
  public drawFloor() {
    this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.floorVertexPositonBuffer);
    this.gl.vertexAttribPointer(this.vertexPositionAttribute, 3, this.gl.FLOAT, false, 0, 0);

    this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.floorVertexNormalBuffer);
    this.gl.vertexAttribPointer(this.vertexNormalAttribute, 3, this.gl.FLOAT, false, 0, 0);

    this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.cubeVertexColorBuffer);
    this.gl.disableVertexAttribArray(this.vertexColorAttribute);
    this.gl.vertexAttrib4f(this.vertexColorAttribute, 1.0, 0.0, 0.0, 1.0);

    this.setupFloorTransformMatrix();
    this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
  }
  public setupFloorTransformMatrix() {
    this.floorModelViewMatrix = mat4.create();
    this.floorProjectionMatrix = mat4.create();
    mat4.identity(this.floorModelViewMatrix);
    mat4.lookAt(this.floorModelViewMatrix, [15, 15, 15], [0, 0, 0], [0, 1, 0]);
    mat4.scale(this.floorModelViewMatrix, this.floorModelViewMatrix, [4.0, 4.0, 4.0]);
    mat4.perspective(this.floorProjectionMatrix, 70, this.rect!.width / this.rect!.height, 0.1, 100.0);
    this.gl.uniformMatrix4fv(this.uniformMVMatrix, false, this.floorModelViewMatrix);
    this.gl.uniformMatrix4fv(this.uniformProjectMatrix, false, this.floorProjectionMatrix);

    this.floorNormalMatrix = mat3.create();
    mat3.fromMat4(this.floorNormalMatrix, this.floorModelViewMatrix);
    mat3.transpose(this.floorNormalMatrix, this.floorNormalMatrix);
    this.gl.uniformMatrix3fv(this.uniformNormalMatrixLoc, false, this.floorNormalMatrix);
  }
  public setupLights() {
    this.gl.uniform3fv(this.uniformLightPositionLoc, [10.0, 10.0, 10.0]);
    this.gl.uniform3fv(this.uniformAmbientLightColorLoc, [0.5, 0.5, 0.5]);
    this.gl.uniform3fv(this.uniformDiffuseLightColorLoc, [0.7, 0.7, 0.7]);
    this.gl.uniform3fv(this.uniformSpecularLightColorLoc, [1.0, 1.0, 1.0]);
  }
  public setupBuffers() {
    this.floorVertexPositonBuffer = this.gl.createBuffer();
    this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.floorVertexPositonBuffer);
    const floorVertexPositon = [
      1.0, 0.0, 1.0,
      1.0, 0.0, -1.0,
      -1.0, 0.0, 1.0,
      -1.0, 0.0, -1.0,
    ];
    this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(floorVertexPositon), this.gl.STATIC_DRAW);

    this.floorVertexNormalBuffer = this.gl.createBuffer();
    this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.floorVertexNormalBuffer);
    const floorVertexNormals = [
      0.0, 1.0, 0.0,
      0.0, 1.0, 0.0,
      0.0, 1.0, 0.0,
      0.0, 1.0, 0.0,
    ];
    this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(floorVertexNormals), this.gl.STATIC_DRAW);// 3 24

    this.cubeVertexPositionBuffer = this.gl.createBuffer();
    this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.cubeVertexPositionBuffer);
    const cubeVertexPosition = [
    // Front face
      -1.0, -1.0,  1.0,
      1.0, -1.0,  1.0,
      1.0,  1.0,  1.0,
      -1.0,  1.0,  1.0,
      // Back face
      -1.0, -1.0, -1.0,
      -1.0,  1.0, -1.0,
      1.0,  1.0, -1.0,
      1.0, -1.0, -1.0,
      // Top face
      -1.0,  1.0, -1.0,
      -1.0,  1.0,  1.0,
      1.0,  1.0,  1.0,
      1.0,  1.0, -1.0,
      // Bottom face
      -1.0, -1.0, -1.0,
      1.0, -1.0, -1.0,
      1.0, -1.0,  1.0,
      -1.0, -1.0,  1.0,
      // Right face
      1.0, -1.0, -1.0,
      1.0,  1.0, -1.0,
      1.0,  1.0,  1.0,
      1.0, -1.0,  1.0,
      // Left face
      -1.0, -1.0, -1.0,
      -1.0, -1.0,  1.0,
      -1.0,  1.0,  1.0,
      -1.0,  1.0, -1.0,
    ];
    this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(cubeVertexPosition), this.gl.STATIC_DRAW); // 3 24

    this.cubeVertexIndexBuffer = this.gl.createBuffer();
    this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.cubeVertexIndexBuffer);
    const cubeVertexIndex = [
      0,  1,  2,      0,  2,  3,    // front
      4,  5,  6,      4,  6,  7,    // back
      8,  9,  10,     8,  10, 11,   // top
      12, 13, 14,     12, 14, 15,   // bottom
      16, 17, 18,     16, 18, 19,   // right
      20, 21, 22,     20, 22, 23,    // left
    ];
    this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cubeVertexIndex), this.gl.STATIC_DRAW);// 1 36

    this.cubeVertexColorBuffer = this.gl.createBuffer();
    this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.cubeVertexColorBuffer);
    // 前红 后绿 上蓝 下黄 右浅蓝 左紫
    const colors = [
      [1.0, 0.0, 0.0, 1.0],
      [0.0, 1.0, 0.0, 1.0],
      [0.0, 0.0, 1.0, 1.0],
      [1.0, 1.0, 0.0, 1.0],
      [0.0, 1.0, 1.0, 1.0],
      [1.0, 0.0, 1.0, 1.0],
    ];
    let cubeVertexColor: number[] = [];
    for (let i = 0;i < 6;i++) {
      const c = colors[i];
      for (let j = 0;j < 4;j++) {
        cubeVertexColor = cubeVertexColor.concat(c);
      }
    }
    this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(cubeVertexColor), this.gl.STATIC_DRAW);

    this.cubeVertexNormalBuffer = this.gl.createBuffer();
    this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.cubeVertexNormalBuffer);
    const cubeVertexNormals = [
      // front
      0.0, 0.0, 1.0,
      0.0, 0.0, 1.0,
      0.0, 0.0, 1.0,
      0.0, 0.0, 1.0,
      // back
      0.0, 0.0, -1.0,
      0.0, 0.0, -1.0,
      0.0, 0.0, -1.0,
      0.0, 0.0, -1.0,
      // top
      0.0, 1.0, 0.0,
      0.0, 1.0, 0.0,
      0.0, 1.0, 0.0,
      0.0, 1.0, 0.0,
      // bottom
      0.0, -1.0, 0.0,
      0.0, -1.0, 0.0,
      0.0, -1.0, 0.0,
      0.0, -1.0, 0.0,
      // right
      1.0, 0.0, 0.0,
      1.0, 0.0, 0.0,
      1.0, 0.0, 0.0,
      1.0, 0.0, 0.0,
      // left
      -1.0, 0.0, 0.0,
      -1.0, 0.0, 0.0,
      -1.0, 0.0, 0.0,
      -1.0, 0.0, 0.0,
    ];
    this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(cubeVertexNormals), this.gl.STATIC_DRAW);// 3 24

    this.gl.enableVertexAttribArray(this.vertexPositionAttribute);
    this.gl.enableVertexAttribArray(this.vertexColorAttribute);
    this.gl.enableVertexAttribArray(this.vertexNormalAttribute);
  }
  public setupShaders() {
    const vertexShaderSource = `precision mediump float;
                                attribute vec3 aVertexPosition;
                                attribute vec3 aVertexNormal;
                                attribute vec4 aVertexColor;
                                uniform mat4 uMVMatrix;
                                uniform mat4 uPMatrix;
                                uniform mat3 uNMatrix;
                                uniform vec3 uLightPosition;
                                uniform vec3 uAmbientLightColor;
                                uniform vec3 uDiffuseLightColor;
                                uniform vec3 uSpecularLightColor;
                                varying vec4 vColor;
                                varying vec3 vLightWeighting;
                                const float shininess = 32.0;
                                void main(){
                                  vColor = aVertexColor;
                                  vec4 vertexPositionEye4 = uMVMatrix * vec4(aVertexPosition,1.0);
                                  vec3 vertexPositionEye3 = vertexPositionEye4.xyz / vertexPositionEye4.w;
                                  vec3 vectorToLightSource = normalize(uLightPosition - vertexPositionEye3);
                                  vec3 normalEye = normalize(uNMatrix * aVertexNormal);
                                  float diffuseLightWeighting = max(dot(normalEye,vectorToLightSource),0.0);
                                  vec3 reflectVector = normalize(reflect(-vectorToLightSource,normalEye));
                                  vec3 viewVectorEye = -normalize(vertexPositionEye3);
                                  float rdotv = max(dot(reflectVector,viewVectorEye),0.0);
                                  float specularLightWeighting = pow(rdotv,shininess);
                                  vLightWeighting = uAmbientLightColor + uDiffuseLightColor * diffuseLightWeighting + uSpecularLightColor * specularLightWeighting;
                                  gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition,1.0);
                                }`;
    const fragmentShaderSource = `precision mediump float;
                                  varying vec3 vLightWeighting;
                                  varying vec4 vColor;
                                  void main(){     
                                    gl_FragColor = vColor * vec4(vLightWeighting.rgb,1.0);
                                    // gl_FragColor = vec4(1.0);
                                  }`;
    const vertexShader = this.loadShader(this.gl.VERTEX_SHADER, vertexShaderSource);
    const fragmentShader = this.loadShader(this.gl.FRAGMENT_SHADER, fragmentShaderSource);
    this.shaderProgram = this.gl.createProgram();
    this.gl.attachShader(this.shaderProgram!, vertexShader!);
    this.gl.attachShader(this.shaderProgram!, fragmentShader!);
    this.gl.linkProgram(this.shaderProgram!);
    if (!this.gl.getProgramParameter(this.shaderProgram!, this.gl.LINK_STATUS)) {
      alert('Failed to setup shaders');
    }
    this.gl.useProgram(this.shaderProgram!);

    this.vertexPositionAttribute = this.gl.getAttribLocation(this.shaderProgram!, 'aVertexPosition');
    this.vertexColorAttribute =  this.gl.getAttribLocation(this.shaderProgram!, 'aVertexColor');
    this.vertexNormalAttribute = this.gl.getAttribLocation(this.shaderProgram!, 'aVertexNormal');
    this.uniformMVMatrix = this.gl.getUniformLocation(this.shaderProgram!, 'uMVMatrix')!;
    this.uniformProjectMatrix = this.gl.getUniformLocation(this.shaderProgram!, 'uPMatrix')!;
    this.uniformNormalMatrixLoc = this.gl.getUniformLocation(this.shaderProgram!, 'uNMatrix')!;
    this.uniformLightPositionLoc = this.gl.getUniformLocation(this.shaderProgram!, 'uLightPosition')!;
    this.uniformAmbientLightColorLoc = this.gl.getUniformLocation(this.shaderProgram!, 'uAmbientLightColor')!;
    this.uniformDiffuseLightColorLoc = this.gl.getUniformLocation(this.shaderProgram!, 'uDiffuseLightColor')!;
    this.uniformSpecularLightColorLoc = this.gl.getUniformLocation(this.shaderProgram!, 'uSpecularLightColor')!;
  }
  public loadShader(type: number, shaderSource: any) {
    const shader = this.gl.createShader(type);
    this.gl.shaderSource(shader!, shaderSource);
    this.gl.compileShader(shader!);
    if (!this.gl.getShaderParameter(shader!, this.gl.COMPILE_STATUS)) {
      alert(`Error Compiling shader${this.gl.getShaderInfoLog(shader!)}`);
      this.gl.deleteShader(shader);
      return null;
    }
    return shader;
  }
  public mounted() {
    this.rect = (this.$el as HTMLDivElement).getBoundingClientRect();
    this.gl = (this.$refs.myGLCanvas as HTMLCanvasElement).getContext('webgl') as WebGLRenderingContext;
    this.setupShaders();
    this.setupBuffers();
    this.setupLights();
    this.gl.enable(this.gl.DEPTH_TEST);
    this.gl.clearColor(0, 0, 0, 1);
    this.gl.enable(this.gl.CULL_FACE);
    this.gl.viewport(0, 0, this.rect!.width, this.rect!.height);
    this.draw();
  }
}
</script>

<style lang="postcss" scoped>
.webGlPrc4{
    touch-action: none;
    position: absolute;
    left: 0;top: 0;
    width: 100%;
    height: 100%;
    background-color: grey;
}
</style>
